<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Windows Application Test System Using Python</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link href="../tac.css" rel="stylesheet" type="text/css">
</head>

<body>
<img src="../images/tizmoi.jpg">
<H2>WATSUP - Windows Application Test System Using Python</H2>

The WATSUP toolkit is designed to allow the automated test of Windows applications. 
The system uses the "object-based" mechanism for identifying and invoking actions 
on controls and menu items. 
<P>So much has been written about the scope, robustness, scalability and outstanding 
  usability of Python that I'll go no further with it here, only to say that if 
  you haven't yet had a chance to use this comprehensive, open source language, 
  don't miss out on the opportunity to take a serious look at it!</P>
 
 <p>The examples in this document assume a basic familiarity with Python.</p>
 


<H2>Functional Tests</H2>

Testers/developers write automated functional tests which follow a prescriptive, 
possibly branching, possibly dynamic "user workflow". The script can check for 
changes in the gui itself, operating system environment, file system, database 
table and records, network, internet or extranet urls/pages/web services ... - in 
fact anywhere that there could be changes. 
<p>
Examination of the functions in module autoWinGui.py within the watsup package shows the variety of windows control items
that can be checked/modified. These include:
<ul>
<li>Get and set text in editable controls</li>
<li>Edit and select items from controls supporting lists</li>
<li>Click and double-click controls to invoke their actions</li>
<li>Determine the state of menu items and invoke them</li>
</ul>
<p> The system also provides tools for finding windows by caption and/or class, 
  controls by text/caption and/or class, and menu items by text or position. (One 
  of the aspirations of this project is to continue to extend the list to include 
  as many controls as possible) .</p>
  
<H3>Example 1 - automated writing on Notepad</H3>  
<p>Here's a simple example of the control over applications that you can have with watsup. 
First, launch notepad from:</p> <p>Windows Start Menu - All Programs - Accessories - Notepad</p>
Then run the following script (<a href="code/example1.py">Example 1</a>)
<code>
<pre>
from watsup.winGuiAuto import findTopWindow,findControl,setEditText
from time import sleep  
# Locate notepad's edit area, and enter various bits of text.

notepadWindow = findTopWindow(wantedClass='Notepad')
editArea = findControl(notepadWindow,wantedClass="Edit") 
setEditText(editArea, "Hello, again!")  
sleep(0.8) 
setEditText(editArea, " You still there?",True) 
 </pre></code> 
 Finally, close notepad.<p></p> 
 
 <h3>Example 2 - testing a simple example </h3>
 
In functional tests, the tester wants to ensure that 
the cause - invoking a sequence of windows events (button clicks, menu item activation) 
has the predicted effect of, for example, a change in a value of a sindows control,
the creation of a file, or the entry of a new database record. 
See the directory watsup/examples/simple directory for the executable simple.exe.

If you run the application, you see a button and a text box. 
Enter a valid filename into the box, say xyz, and
click the button; 
after the file is created, a message box appears containing a success message,
and investigation of the directory watsup/examples/simple will show a file
called 'xyz.txt' has been created (or overwritten).

<p>Now let's script a test to automate this functionality. </p>

First find and launch the application. 
Then run the following script (<a href="code/example2.py">Example 2</a>)
<code><pre>
from watsup.winGuiAuto import findControl,setEditText, findTopWindow,clickButton
import os
import os.path

FILENAME='atestfile.txt'

def main():
    # delete any occurrence of this file from the disk
    if os.path.exists(FILENAME):
        os.remove(FILENAME)
        
    form=findTopWindow(wantedText='Simple Form')
    button=findControl(form,wantedText='Create file')
    editbox=findControl(form,wantedClass='TEdit')
    
    # enter a filename:
    setEditText(editbox,[FILENAME])
    print 'clicking button to create file'
		clickButton(button)
    
    # now check that the file is there
    if os.path.exists(FILENAME):
        print 'file %s is present' %FILENAME
    else:
        print "file %s isn't there" % FILENAME     

if __name__=='__main__':
    main()    


</pre>
</code>

 
<h3>Example 3 - automating program launch and termination</h3>

<p>It's a bit tedious having to start and close the application each time. 
<a href="code/example3.py">Example 3</a> launches the application, 
if it isn't already running, and terminates it on 
completion of the test</p>

<code><pre>
from watsup.launcher import launchApp,terminateApp
from watsup.winGuiAuto import findTopWindows                            
import example2

# find an instance of SimpleForm. If one isn't there, launch it

forms=findTopWindows(wantedText='Simple Form')
if forms:
    form=forms[0]
else:
    form=launchApp('simple.exe',wantedText='Simple Form')    

example2.main()

# and terminate the form
terminateApp(form)    

</pre></code>

launchApp starts the application in a separate thread, 
and looks for a window with caption containing "Simple Form", 
returning the window handle of the recovered form.

terminateApp attempts to close the form, by trying to activate menu item File-Exit, or, failing that,
sending Alt + F4.

<H3>Example 4 - finding windows and controls</H3>

<p>In building scripts, we need to be able to find the class and/or text of the many windows and controls 
to be investigated or invoked.</p>

<p>In the tools directory, there's a tool - ShowWindows.bat - to assist us with this.</p>
<img src="images/ShowWindows1.jpg" alt="Show Windows 1" />


 <p>Clicking the "Register" button persists information about 
the existing windows running on the system (and it tells you how many, FYI). 
Clicking the "Find new" button will report all non-trivial windows and all their 
constituent controls which have appeared in the windows environment 
since the last "Register" click.

So to test our program simple.exe, launch ShowWindows, click Register. 
Then launch simple.exe and 
and click the Find New button. 
The associated text box shows n/m, where m is the total number of new windows found,
and n is the number of those which are significant (ie have any controls) and are reported. </p>

<img src="images/ShowWindows2.jpg" alt="Show Windows 2" />


<H2>Performance Tests</H2>

<p>Performance tests, in this definition, are single-client scripts, 
which are similar in operation to the functional tests above, 
but for which certain steps of the tests 
must either be done within an acceptable timeframe ("CHECKING") 
and/or the time taken for those steps 
should be recorded for subsequent analysis ("RECORDING").</p>

<p>WATSUP provides a simple mechanism to add such tests to existing functional tests. 
In examples 4a & 4b, we launch almost identical applications, 
perform.exe and perform2.exe respectively.
 When the button is clicked, the text "Finished" is written
to the edit box. The difference between the programs is that the former is
 coded to wait for 1/2 second before "Finished"
appears; in the latter case, the delay is 1.5 seconds.</p>

<p>In both cases, we are setting a performance test that the process 
take no more than 1 second. 
Clearly, we should expect example 4a to be ok and example 4b to fail.
</p>


<p>So we have <a href="code/example4a.py">Example 4a</a></p>
<code><pre>
from example4 import main

main('perform.exe','Performance Form 1')

</pre></code>
<p>and <a href="code/example4b.py">Example 4b</a></p>
<code><pre>
from example4 import main

main('perform2.exe','Performance Form 2')

</pre></code>

<p>which reference <a href="code/example4.py">Example 4</a>:</p>
<code><pre>

from watsup.launcher import launchApp,terminateApp 
from watsup.winGuiAuto import findTopWindows, findControl,getEditText,clickButton  
from watsup.performance import PerformanceCheck,PerformanceCheckError  
from time import sleep,time                      

def main(myExecutable,myWantedText):
    # find an instance of SimpleForm. If one isn't there, launch it
    forms=findTopWindows(wantedText=myWantedText)
    if forms:
        form=forms[0]
    else:
        form=launchApp(myExecutable,wantedText=myWantedText)       
    
    button=findControl(form,wantedText='Click me')
    editbox=findControl(form,wantedClass='TEdit')

    #start a performance check instance
    p=PerformanceCheck()
    
    clickButton(button)    
    
    # belts and braces to avoid infinite waiting!
    maxWaitTime=2.0
    startTime=time()
    
    while time()-startTime&lt;maxWaitTime:
        t=getEditText(editbox)
        if t:
            break
        else:
            sleep(0.1)
    else:
        raise Exception,'Failed to get value after maxWaitTime of %s secs' % maxWaitTime        
    
    try:
        try:
           #do the check/recording step, identifying this step with the wantedtext
            p.check(myWantedText,1.0)    
        except PerformanceCheckError,e:
            print '** Failed: %s' % e
            
    # and terminate the form
    finally:
        terminateApp(form)      

if __name__=='__main__':
    print ' please run example4a or 4b'

</pre></code>

<h4>Key points in example4.py</h4>

<p>Immediately prior to clicking the button, we establish a PerformanceCheck instance, 
which, among other things, establises a timer. 
Every time the check() method is called on a PerformanceCheck instance, the following occurs:</p>

<p>Also if we are CHECKING (the default condition), 
that time is checked against the number of seconds added as the
second parameter. 
If the elapsed time exceeds the value of the 2nd parameter, an exception is raised.</p>

<p>So in example4.py, we see that the check method requires a 1 second acceptance.
Hence example4a succeeds and example4b fails.
</p>

<H2>Regression Testing</H2>

<p>Functional and performance scripts such as those above are 
immediately useable within a test framework -
the excellent python unit test framework is highly recommended
(unittest.py, which comes along with your python installation). 
This then enables the tester to develop complete regression tests
involving any combination of Functional & Performance testing. </p>
<p>For an example of the use of functional and performance tests within 
the unit test framework, run the program framework.bat in the tools subdirectory. 
This will launch a nice user interface from which sets of test cases can be run: 
</p>
<img src="images/framework1.jpg">

<p>Select the File menu option, and you have the option to load files which 
contain testcases, or directories/directory trees. If select the "Load files"
option, and navigate up one directory, and then down through examples and the unittests 
directories, you should find, and select, exampleTests.py. 
The Framework program examines the selected file(s) and extracts the TestCases, 
presents in the top frame</p>

<img src="images/framework2.jpg"> 

<p>Selecting Menu option Testing - Run Tests (or the long button labelled Run Tests),
 will cause each test to be run; success or failure is shown in the lower frame</p>

<img src="images/framework3.jpg"> 
<p>For more information on python's unit test module, refer to the python documentation.
Finally, it is worth noting that this framework will work with any unit tests, not just 
those specifically testing windows application, so you can test 
elements of the logic that you have written in other applications. 

<H2>Downloads</H2>

Download <a href="../downloads/watsup-0.4.zip">watsup</a> here
<p>(This package should be unzipped in the site-packages directory in your python installation)</p>

<H3> Dependencies</H3>
 <ul>
 <li><a href="http://www.python.org/download"> Python</a> (version at least 2.3)</li>
 <li><a href="http://sourceforge.net/projects/pywin32">pywin32 </a></li>
<li><a href="http://sourceforge.net/project/showfiles.php?group_id=71702">ctypes </a></li>
<li><a href="http://www.rutherfurd.net/python/sendkeys/#binaries" >SendKeys </a></li>
<li><a href="http://www.wxpython.org/download.php" >wxPython library</a>  (for tools)</li>
 </ul>
 
 <h3>Credits</h3>
 
<p> The framework test tool was built from parts in unittestgui.py by Chris Liechti.
Much of the core functionality in WATSUP derives from the important work by 
  <a href="http://www.brunningonline.net/simon/python/index.html">Simon Brunning</a>, 
  who, in his module winGuiAuto.py provided concise, accessible mechanisms to 
  access and "click" windows controls & menus; Simon's work recognises the huge 
  contribution that Mark Hammond has made to the python community (and wider) 
  in providing pywin32, comprehensive win32 API modules for python. 
	</p>
	 
Dr Tim Couper<br/>
<a href="mailto:timc@tizmoi.net">timc@tizmoi.net</a>

</body>
</html>
